package com.base.system.base.db;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.base.system.base.db.groups.GroupBy;
import com.base.system.base.db.orders.AscOrder;
import com.base.system.base.db.orders.DescOrder;
import com.base.system.base.db.orders.OrderBy;
import com.base.system.base.db.specifications.*;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;

/**
 * 暂时查询条件不全，如需请自觉补全，留言：fengbenwu
 * 提示：凡是使用了join的查询，建议使用distinct、group by进行去重！
 */
public class JoinSql<T> {
    private List<Specification<T>> specifications = new ArrayList<>();
    private JoinType joinType = JoinType.INNER;
    private boolean isDistinct = false;
    private List<GroupBy> grouping = new ArrayList<>();
    private List<OrderBy> orders = new ArrayList<>();

    public JoinSql(Class<T> tClass) {
    }

    public static <T> JoinSql<T> genNewSql(Class<T> tClass) {
        return new JoinSql<>(tClass);
    }

    /**
     * 切换至join属性查询并指定join的类型
     */
    public JoinSql<T> join(JoinType joinType) {
        this.joinType = joinType;
        return this;
    }

    /**
     * 大于
     */
    public JoinSql<T> gt(String attr, Number value) {
        gt(true, attr, value);
        return this;
    }

    /**
     * find_in_set
     */
    public JoinSql<T> find_in_set(String attr, Object... value) {
        if (ArrayUtil.isNotEmpty(value)) addSpecification(true, new FindInSetSpecification<>(attr, joinType, value));
        return this;
    }

    /**
     * & 与
     *
     * @return
     */
    public JoinSql<T> pd(String attr, Object value) {
        if (!ObjectUtil.isEmpty(value)) addSpecification(true, new IntersectionSpecification<>(attr, joinType, value));
        return this;
    }

    /**
     * & 与
     *
     * @return
     */
    public JoinSql<T> pd(String attr, Object value, String baseAttr) {
        if (!ObjectUtil.isEmpty(value))
            addSpecification(true, new IntersectionSpecification<>(attr, joinType, value, baseAttr));
        return this;
    }

    /**
     * & 与
     *
     * @return
     */
    public JoinSql<T> sub(String attr, Object value) {
        if (!ObjectUtil.isEmpty(value)) addSpecification(true, new SubSpecification<>(attr, joinType, value));
        return this;
    }

    /**
     * 大于
     */
    public JoinSql<T> gt(boolean condition, String attr, Number value) {
        if (!ObjectUtil.isEmpty(value)) addSpecification(condition, new GtSpecification<>(attr, value, joinType));
        return this;
    }

    /**
     * 大于等于
     */
    public JoinSql<T> ge(String attr, Number value) {
        ge(true, attr, value);
        return this;
    }

    /**
     * 大于等于
     */
    public JoinSql<T> ge(boolean condition, String attr, Number value) {
        if (!ObjectUtil.isEmpty(value)) addSpecification(condition, new GeSpecification<>(attr, value, joinType));
        return this;
    }

    /**
     * 小于
     */
    public JoinSql<T> lt(String attr, Number value) {
        lt(true, attr, value);
        return this;
    }

    /**
     * 小于
     */
    public JoinSql<T> lt(boolean condition, String attr, Number value) {
        if (!ObjectUtil.isEmpty(value)) addSpecification(condition, new LtSpecification<>(attr, value, joinType));
        return this;
    }

    /**
     * 小于等于
     */
    public JoinSql<T> le(String attr, Number value) {
        le(true, attr, value);
        return this;
    }

    /**
     * 小于等于
     */
    public JoinSql<T> le(boolean condition, String attr, Number value) {
        if (!ObjectUtil.isEmpty(value)) addSpecification(condition, new LeSpecification<>(attr, value, joinType));
        return this;
    }

    /**
     * 等于,多个值表示进行or操作
     */
    public JoinSql<T> eq(String attr, Object... values) {
        eq(true, attr, values);
        return this;
    }

    /**
     * 等于,多个值表示进行or操作
     */
    public JoinSql<T> eqIfHas(String attr, Object value) {
        eq(ObjectUtil.isNotEmpty(value), attr, value);
        return this;
    }

    /**
     * 等于,多个值表示进行or操作
     */
    public JoinSql<T> eq(boolean condition, String attr, Object... values) {
        addSpecification(condition, new EqualSpecification<>(attr, joinType, values));
        return this;
    }

    /**
     * 不等于
     */
    public JoinSql<T> ne(String attr, Object... values) {
        ne(true, attr, values);
        return this;
    }

    /**
     * 不等于
     */
    public JoinSql<T> neIfHas(String attr, Object value) {
        ne(ObjectUtil.isNotEmpty(value), attr, value);
        return this;
    }

    /**
     * 不等于
     */
    public JoinSql<T> ne(boolean condition, String attr, Object... values) {
        addSpecification(condition, new NotEqualSpecification<>(attr, joinType, values));
        return this;
    }

    /**
     * 相似
     */
    public JoinSql<T> like(String attr, String... values) {
        like(true, attr, values);
        return this;
    }

    /**
     * 相似
     */
    public JoinSql<T> likeIfHas(String attr, String value) {
        if (StrUtil.isNotEmpty(value)) like(true, attr, value);
        return this;
    }

    /**
     * 相似
     */
    public JoinSql<T> like(boolean condition, String attr, String... values) {
        for (int i = 0; i < values.length; i++) values[i] = "%" + values[i] + "%";
        addSpecification(condition, new LikeSpecification<>(attr, joinType, values));
        return this;
    }

    /**
     * 右相似
     */
    public JoinSql<T> likeg(String attr, String... values) {
        likeg(true, attr, values);
        return this;
    }

    /**
     * 右相似
     */
    public JoinSql<T> likegIfHas(String attr, String value) {
        if (StrUtil.isNotEmpty(value)) likeg(true, attr, value);
        return this;
    }

    /**
     * 右相似
     */
    public JoinSql<T> likeg(boolean condition, String attr, String... values) {
        for (int i = 0; i < values.length; i++) values[i] = "%" + values[i];
        addSpecification(condition, new LikeSpecification<>(attr, joinType, values));
        return this;
    }

    /**
     * 左相似
     */
    public JoinSql<T> likel(String attr, String... values) {
        likel(true, attr, values);
        return this;
    }

    /**
     * 左相似
     */
    public JoinSql<T> likelIfHas(String attr, String value) {
        if (StrUtil.isNotEmpty(value)) likel(true, attr, value);
        return this;
    }

    /**
     * 左相似
     */
    public JoinSql<T> likel(boolean condition, String attr, String... values) {
        for (int i = 0; i < values.length; i++) values[i] = values[i] + "%";
        addSpecification(condition, new LikeSpecification<>(attr, joinType, values));
        return this;
    }

    /**
     * 不相似
     */
    public JoinSql<T> notLike(String attr, String... values) {
        notLike(true, attr, values);
        return this;
    }

    /**
     * 不相似
     */
    public JoinSql<T> notLikeIfHas(String attr, String value) {
        if (StrUtil.isNotEmpty(value)) notLike(true, attr, value);
        return this;
    }

    /**
     * 不相似
     */
    public JoinSql<T> notLike(boolean condition, String attr, String... values) {
        for (int i = 0; i < values.length; i++) values[i] = "%" + values[i] + "%";
        addSpecification(condition, new NotLikeSpecification<>(attr, joinType, values));
        return this;
    }

    /**
     * 右不相似
     */
    public JoinSql<T> notLikeg(String attr, String... values) {
        notLikeg(true, attr, values);
        return this;
    }

    /**
     * 右不相似
     */
    public JoinSql<T> notLikegIfHas(String attr, String value) {
        if (StrUtil.isNotEmpty(value)) notLikeg(true, attr, value);
        return this;
    }

    /**
     * 右不相似
     */
    public JoinSql<T> notLikeg(boolean condition, String attr, String... values) {
        for (int i = 0; i < values.length; i++) values[i] = "%" + values[i];
        addSpecification(condition, new NotLikeSpecification<>(attr, joinType, values));
        return this;
    }

    /**
     * 左不相似
     */
    public JoinSql<T> notLikel(String attr, String... values) {
        notLikel(true, attr, values);
        return this;
    }

    /**
     * 左不相似
     */
    public JoinSql<T> notLikelIfHas(String attr, String value) {
        if (StrUtil.isNotEmpty(value)) notLikel(true, attr, value);
        return this;
    }

    /**
     * 左不相似
     */
    public JoinSql<T> notLikel(boolean condition, String attr, String... values) {
        for (int i = 0; i < values.length; i++) values[i] = values[i] + "%";
        addSpecification(condition, new NotLikeSpecification<>(attr, joinType, values));
        return this;
    }

    /**
     * in操作
     */
    public JoinSql<T> in(String attr, List<Object> values) {
        in(true, attr, values);
        return this;
    }

    /**
     * in操作
     */
    public JoinSql<T> in(boolean condition, String attr, List<Object> values) {
        addSpecification(condition, new InSpecification<>(attr, values, joinType));
        return this;
    }

    /**
     * notIn操作
     */
    public JoinSql<T> notIn(String attr, List<Object> values) {
        notIn(true, attr, values);
        return this;
    }

    /**
     * notIn操作
     */
    public JoinSql<T> notIn(boolean condition, String attr, List<Object> values) {
        addSpecification(condition, new NotInSpecification<>(attr, values, joinType));
        return this;
    }

    /**
     * isNull
     */
    public JoinSql<T> isNull(String attr) {
        isNull(true, attr);
        return this;
    }

    /**
     * isNull
     */
    public JoinSql<T> isNull(boolean condition, String attr) {
        addSpecification(condition, new IsNullSpecification<>(attr, joinType));
        return this;
    }

    /**
     * isNotNull
     */
    public JoinSql<T> isNotNull(String attr) {
        isNotNull(true, attr);
        return this;
    }

    /**
     * isNotNull
     */
    public JoinSql<T> isNotNull(boolean condition, String attr) {
        addSpecification(condition, new IsNotNullSpecification<>(attr, joinType));
        return this;
    }

    /**
     * between
     */
    public JoinSql<T> between(String attr, Object lowValue, Object upValue) {
        between(true, attr, lowValue, upValue);
        return this;
    }

    /**
     * between
     */
    public JoinSql<T> between(boolean condition, String attr, Object lowValue, Object upValue) {
        if (ObjectUtil.isNotEmpty(lowValue) && ObjectUtil.isNotEmpty(upValue))
            addSpecification(condition, new BetweenSpecification<>(attr, lowValue, upValue, joinType));
        return this;
    }

    /**
     * isEmpty
     */
    public JoinSql<T> isEmpty(String attr) {
        isEmpty(true, attr);
        return this;
    }

    /**
     * isEmpty
     */
    public JoinSql<T> isEmpty(boolean condition, String attr) {
        addSpecification(condition, new IsEmptySpecification<>(attr, joinType));
        return this;
    }

    /**
     * isNotEmpty
     */
    public JoinSql<T> isNotEmpty(String attr) {
        isNotEmpty(true, attr);
        return this;
    }

    /**
     * isNotEmpty
     */
    public JoinSql<T> isNotEmpty(boolean condition, String attr) {
        addSpecification(condition, new IsNotEmptySpecification<>(attr, joinType));
        return this;
    }

    /**
     * 去重
     */
    public JoinSql<T> distinct() {
        isDistinct = true;
        return this;
    }

    /**
     * group by
     */
    public JoinSql<T> groupBy(List<String> attrs) {
        attrs.forEach(group -> {
            grouping.add(new GroupBy(group, joinType));
        });
        return this;
    }

    /**
     * group by
     */
    public JoinSql<T> groupBy(String... attrs) {
        for (String group : attrs) grouping.add(new GroupBy(group, joinType));
        return this;
    }

    /**
     * order by desc
     */
    public JoinSql<T> desc(String... attrs) {
        for (String orderBy : attrs) orders.add(new DescOrder(orderBy, joinType));
        return this;
    }

    /**
     * order by asc
     */
    public JoinSql<T> asc(String... attrs) {
        for (String orderBy : attrs) orders.add(new AscOrder(orderBy, joinType));
        return this;
    }

    private void addSpecification(boolean condition, Specification<T> specification) {
        if (condition) specifications.add(specification);
    }

    /**
     * 生成Specification
     */
    public Specification<T> build() {
        return toSpec();
    }

    /**
     * 生成Specification
     */
    public Specification<T> toSpec() {
        return (root, query, cb) -> {
            Predicate[] predicates = new Predicate[specifications.size()];
            for (int i = 0; i < predicates.length; i++)
                predicates[i] = specifications.get(i).toPredicate(root, query, cb);
            //添加group by
            if (!grouping.isEmpty()) {
                Path[] paths = new Path[grouping.size()];
                for (int i = 0; i < paths.length; i++) paths[i] = grouping.get(i).toExpression(root, query, cb);
                query.groupBy(paths);
            }
            //去重
            query.distinct(isDistinct);
            //添加order by
            if (!orders.isEmpty()) {
                Order[] orderArray = new Order[orders.size()];
                for (int i = 0; i < orderArray.length; i++) orderArray[i] = orders.get(i).toOrder(root, query, cb);
                query.orderBy(orderArray);
            }
            return cb.and(predicates);
        };
    }
}
